using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using Commons;
using WPFBPMN;
using WPFBPMN.Utils;

namespace CustomControls
{

    public class BPMNShape : Canvas
    {

        #region Fields
        public static DependencyProperty WidthProperty;
        public static DependencyProperty HeightProperty;
        public static DependencyProperty BPMNNameProperty;
        ShapeType bpmnShapeType = ShapeType.BPMNShape;
        Brush defaultBackground = Brushes.White;
        bool interactsWithBPMN = true;
        bool lockSizeRatio = false;
        bool lockMoving = false;
        bool canBeUnlocked = true;
        Size shapeDefaultSize = new Size(40, 40);
        Shape shape;
        string brushKey;
        BPMNShape shapeParent;
        bool dragging = false;
        Point offset = new Point();
        Point[] connectingPoints ={ new Point(), new Point(), new Point(), new Point() };
        List<BPMNConnectingObject> connectingObjects = new List<BPMNConnectingObject>();
        StartMouseDownInformation startMouseDownInformation = new StartMouseDownInformation();
        BPMNShape currentWorkingElement = null;
        BPMNShape currentSelectedElement = null;
        ShapeType containerType = ShapeType.BPMNTopLevel;

        #endregion

        #region Properties
        public virtual UIElementCollection Children
        {
            get
            {
                return base.Children;
            }
        }
        public ShapeType BPMNShapeType
        {
            get
            {
                return bpmnShapeType;
            }
            set
            {
                bpmnShapeType = value;
            }
        }

        public bool CanBeUnlocked
        {
            get
            {
                return canBeUnlocked;
            }
            set
            {
                canBeUnlocked = value;
            }
        }

        public virtual Brush Background
        {
            get
            {
                return base.Background;
            }
            set
            {
                base.Background = value;
            }
        }

        public Brush DefaultBackground
        {
            get
            {
                return defaultBackground;
            }
        }

        public bool InteractsWithBPMN
        {
            get
            {
                return interactsWithBPMN;
            }
            set
            {
                interactsWithBPMN = value;
            }
        }

        public bool LockSizeRatio
        {
            get
            {
                return lockSizeRatio;
            }
            set
            {
                lockSizeRatio = value;
            }
        }

        public bool LockMoving
        {
            get
            {
                return lockMoving;
            }
            set
            {
                lockMoving = value;
            }
        }

        public Size ShapeDefaultSize
        {
            get
            {
                return shapeDefaultSize;
            }
            set
            {
                shapeDefaultSize = value;
            }
        }

        public Shape Shape
        {
            get
            {
                return shape;
            }
            set
            {
                shape = value;
            }

        }

        public string BrushKey
        {
            get
            {
                return brushKey;
            }
            set
            {
                brushKey = value;
            }
        }

        public void BindBrush()
        {
            if (brushKey != null && brushKey != String.Empty)
            {
                Binding binding = new Binding();
                binding.Source = FindResource(brushKey);
                shape.SetBinding(Shape.FillProperty, binding);
            }
        }

        public BPMNShape ShapeParent
        {
            get
            {
                return shapeParent;
            }
            set
            {
                shapeParent = value;
            }
        }

        public List<BPMNConnectingObject> ConnectingObjects
        {
            get
            {
                return connectingObjects;
            }
            set
            {
                connectingObjects = value;
            }
        }

        public Point[] ConnectingPoints
        {
            get
            {
                return connectingPoints;
            }
            set
            {
                connectingPoints = value;
            }
        }

        public bool Dragging
        {
            get
            {
                return dragging;
            }
            set
            {
                dragging = false;
            }
        }

        #endregion

        #region Constructors

        public BPMNShape(BPMNShape parent)
        {
            this.shapeParent = parent;
        }

        static BPMNShape()
        {
            BPMNShape.WidthProperty = DependencyProperty.Register("Width", typeof(double), typeof(BPMNShape), new PropertyMetadata(new PropertyChangedCallback(OnWidthChanged)));
            BPMNShape.HeightProperty = DependencyProperty.Register("Height", typeof(double), typeof(BPMNShape), new PropertyMetadata(new PropertyChangedCallback(OnHeightChanged)));
            BPMNShape.BPMNNameProperty = DependencyProperty.Register("BPMNName", typeof(string), typeof(BPMNShape), new PropertyMetadata(new PropertyChangedCallback(OnBPMNNameChanged)));
        }
        [BPMNProperty]
        public double Width
        {
            get
            {
                return (double)GetValue(BPMNShape.WidthProperty);
            }
            set
            {
                SetValue(BPMNShape.WidthProperty, value);
            }
        }
        [BPMNProperty]
        public string BPMNName
        {
            get
            {
                return (string)GetValue(BPMNShape.BPMNNameProperty);
            }
            set
            {
                SetValue(BPMNShape.BPMNNameProperty, value);
            }
        }
        [BPMNProperty]
        public double Height
        {
            get
            {
                return (double)GetValue(BPMNShape.HeightProperty);
            }
            set
            {
                SetValue(BPMNShape.HeightProperty, value);
            }
        }

       

        #endregion

        #region Override

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            if (WindowMain.WaitingForUserInteraction|| e.ChangedButton == MouseButton.Right)
            {
                e.Handled = true;
                return;
            }

            if (interactsWithBPMN && !LockMoving && shapeParent != null)
            {
                shapeParent.CurrentWorkingElement = this;
                offset = e.GetPosition(this);
                dragging = true;
                if (ShapeParent.BPMNShapeType == ShapeType.BPMNLane)
                {
                    e.Handled = true;
                    offset.Offset((ShapeParent as BPMNContainer).Title.Height, 0);
                }
            }
            //else
            //{
            //    if (lockMoving && interactsWithBPMN)
            //    {
            //        if (canBeUnlocked)
            //        {
            //            BPMNException exc = new BPMNException(null, BPMNName + " is locked");
            //        }
            //        else
            //        {
            //            BPMNException exc = new BPMNException(null, BPMNName + " cannot be moved");
            //        }
            //    }
            //}
        }
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            if (currentWorkingElement!=null && e.LeftButton == MouseButtonState.Released)
            {
                ReleaseDragging();
            }
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            ReleaseDragging();
            BPMNShape senderBPMNShape = e.Source as BPMNShape;
            if (interactsWithBPMN && senderBPMNShape != null)
            {

                if (senderBPMNShape.ShapeParent != null && this == senderBPMNShape.ShapeParent)
                {
                    WindowMain.SelectedBPMNShape = this;
                }

                if (InteractsWithBPMN && this == senderBPMNShape)
                {
                    WindowMain.SelectedBPMNShape = this;
                }
            }
            else
            {
                if (interactsWithBPMN && SearchFrameworkElementRecursively(e.Source as FrameworkElement, this))
                {
                    WindowMain.SelectedBPMNShape = this;
                }
            }
        }

        private bool SearchFrameworkElementRecursively(FrameworkElement element, BPMNShape parent)
        {
            if (element.Parent != null)
            {
                if (element.Parent == parent)
                {
                    return true;
                }
                else
                {
                    BPMNShape s = element.Parent as BPMNShape;
                    if (s != null)
                    {
                        if (s.interactsWithBPMN)
                        {
                            return false;
                        }
                    }
                    return SearchFrameworkElementRecursively(element.Parent as FrameworkElement, parent);
                }
            }
            return false;
        }

        #endregion

        #region Methods

        public void ReleaseDragging()
        {
            currentWorkingElement = null;
            UpdateConnectingPoints();
            UpdateConnectedObjects();
            if (shapeParent != null)
            {
                shapeParent.CurrentWorkingElement = null;
            }
            dragging = false;
        }

        public BPMNShape CreateNewDrawing(MouseButtonEventArgs e)
        {
            string error;
            if (!(Logic.CanBuildShape(this, WindowMain.DrawingToBuild, out error)))
            {
                BPMNException exc = new BPMNException(null, error);
                return null;
            }

            BPMNShape drawing = CreateDrawingByCommand(WindowMain.DrawingToBuild, this, WindowMain.AdditionalParameters);
            if (drawing != null)
            {
                Children.Add(drawing);
                if (!drawing.LockMoving)
                {
                    Canvas.SetLeft(drawing, e.GetPosition(this).X - drawing.Width / 2);
                    Canvas.SetTop(drawing, e.GetPosition(this).Y - drawing.Height / 2);
                    MoveWithReposition(drawing);
                    PositionShapeOnCreate(drawing, e);
                }
                else
                {
                    BPMNContainer container = (drawing as BPMNContainer);
                    if (container != null)
                    {
                        container.ArangeChildren();
                    }
                }
            }
            return drawing;
        }

        


        public virtual void PositionShapeOnCreate(BPMNShape drawing, MouseButtonEventArgs e)
        {

        }

        public virtual void BPMNInitialize()
        {

            ContextMenu menu = new ContextMenu();
            Binding binding = new Binding();
            XmlDataProvider xmlDataProvider = new XmlDataProvider();
            xmlDataProvider.Source = (FindResource("BPMNShapeContextMenuXml") as XmlDataProvider).Source;
            xmlDataProvider.XPath = String.Format("//Menus/Menu[@Action='ContextMenu{0}']", bpmnShapeType);
            binding.Source = xmlDataProvider;
            binding.XPath = "./*";
            menu.SetBinding(Menu.ItemsSourceProperty, binding);
            ContextMenu = menu;

            Width = shapeDefaultSize.Width;
            Height = shapeDefaultSize.Height;
            if (shape != null)
            {
                shape.Width = shapeDefaultSize.Width;
                shape.Height = shapeDefaultSize.Height;
                this.offset = new Point(shape.Width / 2, this.Height / 2);
            }

            if (shape != null)
            {
                binding = new Binding();
                binding.Source = this;
                binding.Path = new PropertyPath(BPMNShape.WidthProperty);
                shape.SetBinding(Shape.WidthProperty, binding);
                binding = new Binding();
                binding.Source = this;
                binding.Path = new PropertyPath(BPMNShape.HeightProperty);
                shape.SetBinding(Shape.HeightProperty, binding);
                Children.Insert(0, shape);
            }
            BindBrush();
        }

        void miLock_Click(object sender, RoutedEventArgs e)
        {
            lockMoving = (sender as MenuItem).Header == "Lock";
        }

        public void UpdateConnectingPoints()
        {
            connectingPoints[0] = new Point(Canvas.GetLeft(this) + this.Width / 2, Canvas.GetTop(this));
            connectingPoints[1] = new Point(Canvas.GetLeft(this) + this.Width, Canvas.GetTop(this) + this.Height / 2);
            connectingPoints[2] = new Point(Canvas.GetLeft(this) + this.Width / 2, Canvas.GetTop(this) + this.Height);
            connectingPoints[3] = new Point(Canvas.GetLeft(this), Canvas.GetTop(this) + this.Height / 2);
        }

        public void UpdateConnectedObjects()
        {
            foreach (BPMNConnectingObject connectingObject in connectingObjects)
            {
                connectingObject.UpdateLocation();
            }
        }


        public virtual void MoveWithReposition(BPMNShape drawing)
        {

        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (currentWorkingElement != null)
            {
                MoveWithReposition(currentWorkingElement);
            }
        }

        public void MoveTo(double left, double top)
        {
            Canvas.SetLeft(this, left);
            Canvas.SetTop(this, top);
        }

        public void ResizeTo(double _width, double _height)
        {
            if (_width > ShapeDefaultSize.Width & _height > ShapeDefaultSize.Height)
            {
                Width = _width;
                Height = _height;
                shape.Width = _width;
                shape.Height = _height;
            }
        }

        public void SetLayer(int layerIndex)
        {
            Canvas.SetZIndex(this, layerIndex);
        }

        public Point Offset
        {
            get
            {
                return offset;
            }
            set
            {
                offset = value;
            }
        }

        public Brush Color
        {
            get
            {
                return Background;
            }
            set
            {
                Background = value;
            }
        }

        public BPMNShape CurrentWorkingElement
        {
            get
            {
                return currentWorkingElement;
            }
            set
            {
                currentWorkingElement = value;
            }
        }

        public BPMNShape CurrentSelectedElement
        {
            get
            {
                return currentSelectedElement;
            }
            set
            {
                currentSelectedElement = value;
            }
        }

        public StartMouseDownInformation StartInfo
        {
            get { return startMouseDownInformation; }
            set
            {
            }

        }

        public void SendToBack()
        {
            if (ShapeParent != null)
            {
                int min = Canvas.GetZIndex(ShapeParent.Children[0]);
                foreach (UIElement element in ShapeParent.Children)
                {
                    int index = Canvas.GetZIndex(element);
                    if (index < min)
                    {
                        min = index;
                    }
                }
                Canvas.SetZIndex(this, min);
                ShapeParent.Children.Remove(this);
                ShapeParent.Children.Insert(0, this);
            }
        }

        public void BringToFront()
        {
            if (ShapeParent != null)
            {
                int max = 0;
                foreach (UIElement element in ShapeParent.Children)
                {
                    int index = Canvas.GetZIndex(element);
                    if (index > max)
                    {
                        max = index;
                    }
                }
                Canvas.SetZIndex(this, max + 1);
                ShapeParent.Children.Remove(this);
                ShapeParent.Children.Add(this);
            }
        }

        public void Delete()
        {
            if (ShapeParent != null)
            {
                ShapeParent.Children.Remove(this);
                foreach (BPMNConnectingObject obj in ConnectingObjects)
                {
                    ShapeParent.Children.Remove(obj);
                }
                ConnectingObjects.Clear();
                shapeParent.ArangeChildren();
                ArangeChildren();
            }
            else
            {
                BPMNException exc = new BPMNException(null, "Shape has no parent");
            }

        }

        public virtual void ResizeShape()
        {
            for (int i = 0; i < Children.Count; i++)
            {
                BPMNContainer container = Children[i] as BPMNContainer;
                if (container != null)
                {

                    if (container.BPMNShapeType == ShapeType.BPMNPool)
                    {
                        container.Width = this.Width - 20;
                    }
                    else
                    {
                        container.Width = this.Width - container.Title.Height;
                    }
                }
            }
        }

        public virtual void ArangeChildren()
        {
            
            if (bpmnShapeType == ShapeType.BPMNTopLevel &&ShapeParent!=null&& ShapeParent.Children.Count > 0)
            {
                Canvas.SetTop(ShapeParent.Children[0], 10);
                for (int i = 1; i < ShapeParent.Children.Count; i++)
                {
                    BPMNShape previousPool = ShapeParent.Children[i - 1] as BPMNShape;
                    Canvas.SetTop(ShapeParent.Children[i], Canvas.GetTop(previousPool) + previousPool.Height + 10);
                }
                BPMNException exc = new BPMNException(null, "Pools have been rearanged");
            }
        }


        #endregion

        #region Static Methods

        public BPMNShape CreateDrawingByCommand(ShapeType shapeType, BPMNShape parent, List<string> additionalParameters)
        {
            Type type = Type.GetType("CustomControls." + shapeType.ToString());
            Type[] types ={ typeof(BPMNShape),typeof(List<string>) };
            object[] parameters ={ parent,additionalParameters };
            ConstructorInfo constructor = type.GetConstructor(types);
            try
            {
                object obj = constructor.Invoke(parameters);
                BPMNShape newShape = obj as BPMNShape;
                newShape.BPMNInitialize();
                newShape.ArangeChildren();
                newShape.BPMNName = newShape.bpmnShapeType.ToString().Substring(4) + "_" + DateTime.Now.Minute.ToString() + "_" + DateTime.Now.Second.ToString();
                return newShape;
            }
            catch (Exception exc)
            {
                new BPMNException(exc, "Shape could not be created");
            }
            return null;
        }

        public BPMNConnectingObject CreateConnectionByCommand(ConnectionType connectingType, BPMNShape parent)
        {
            Type type = Type.GetType("CustomControls.BPMNConnecting" + connectingType.ToString());
            Type[] types ={ typeof(BPMNShape) };
            object[] parameters ={ parent};
            ConstructorInfo constructor = type.GetConstructor(types);
            try
            {
                object obj = constructor.Invoke(parameters);
                BPMNConnectingObject connectingObj = obj as BPMNConnectingObject;
                return connectingObj;
            }
            catch (Exception exc)
            {
                new BPMNException(exc, "Connecting object could not be created");
            }
            return null;
        }

        public static void OnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            d.SetValue(Canvas.WidthProperty, args.NewValue);
            BPMNShape s = d as BPMNShape;
            (s as Canvas).Width = int.Parse(args.NewValue.ToString());
            if (Commons.Utils.ArrayBPMNContainers.Contains(s.bpmnShapeType))
            {
                (s as BPMNContainer).ArangeChildren();
            }
            if (s != null)
            {
                s.UpdateConnectingPoints();
                s.UpdateConnectedObjects();
                s.ResizeShape();
                if (Commons.Utils.ArrayBPMNContainers.Contains(s.bpmnShapeType))
                {
                    BPMNContainer c = (s as BPMNContainer);
                    c.DrawingCanvas.SetValue(BPMNShape.WidthProperty, (double)args.NewValue - c.Title.Height - 2);
                }
            }
           
        }

        public static void OnHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            d.SetValue(Canvas.HeightProperty, args.NewValue);
            BPMNShape s = d as BPMNShape;
            if (s != null)
            {
                s.UpdateConnectingPoints();
                s.UpdateConnectedObjects();
                s.ResizeShape();
                if (Commons.Utils.ArrayBPMNContainers.Contains(s.bpmnShapeType))
                {
                    (s as BPMNContainer).DrawingCanvas.SetValue(BPMNShape.HeightProperty, (double)args.NewValue - 2);
                }
            }
           
        }

        public static void OnBPMNNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            Commons.Utils.UpdateWindowMainBPMNTV();
        }

        #endregion

    }
    public class BPMNPropertyAttribute : System.Attribute
    {
    }

}